/*
 * jxUtils Project (2013, 2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils.system;

import static org.jxUtils.system.SystemProperties.getOsArchitecture;
import static org.jxUtils.system.SystemProperties.getOsName;
import static org.jxUtils.system.SystemProperties.getOsVersion;
import static org.jxUtils.system.SystemProperties.getUserHome;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.jxUtils.check.CheckUtil;
import org.jxUtils.date.DateUtil;
import org.jxUtils.file.FileSize;
import org.jxUtils.i18n.I18N;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxUtils
 * @package org.jxUtils.system
 * @date 07/09/2013 - 10:52:22
 */
public final class OperatingSystem {
	
	private static final OperatingSystemMXBean SYSTEM_MX_BEAN = ManagementFactory.getOperatingSystemMXBean();
	
	/**
	 * Returns the first readable and writable application data folder appropriate to this
	 */
	public static File getAppDataFolder() {
	
		final File[] folders = getAppDataFolders();
		
		//
		for(final File folder : folders) {
			
			//
			if(folder.canRead() && folder.canWrite()) {
				
				return folder;
			}
		}
		
		return null;
	}
	
	/**
	 * Returns a list of the preferred locations for storing application-specific data in descending order.
	 */
	// TODO: Implementar verificação para o Windows 8
	public static File[] getAppDataFolders() {
	
		final List<File> folders = new ArrayList<File>();
		
		//
		if(isMac()) {
			
			folders.add(new File("/Library/Application Support"));
			
		}else if(isWindows()) {
			
			//
			if(isWinNTSerie()) { // NT/2000/XP
			
				// C:\Documents and Settings\All Users\Application Data
				// Surmise that the "All Users" folder will be a child of the
				// parent of the current user's home folder:
				final File folder = new File(getUserHome()).getParentFile();
				
				folders.add(new File(folder, "All Users\\Application Data"));
				
			}else if(isWin9X()) { // 95/98/ME
			
				// C:\Windows
				folders.add(new File(getUserHome()));
				
			}else if(isWinVista() || isWin7()) {
				
				// C:\ProgramData
				File folder = new File(getUserHome()).getParentFile().getParentFile();
				folders.add(new File(folder, "ProgramData"));
				
				// C:\Users\Public\AppData
				folder = new File(getUserHome()).getParentFile();
				folders.add(new File(folder, "Public\\AppData"));
			}
			
		}else {
			
			folders.add(new File("/var/local"));
			folders.add(new File("/var"));
		}
		
		// folders.addAll(Arrays.asList(listUserDataFolders()));
		final File[] files = new File[folders.size()];
		
		return folders.toArray(files);
	}
	
	/**
	 * @return The Architecture
	 */
	public static String getArchitecture() {
	
		return getOsArchitecture();
	}
	
	/**
	 * @return The Ubuntu or Windows CodeName
	 */
	public static String getCodeName() {
	
		Properties properties = getLSBRelease();
		
		return properties != null && properties.containsKey("DISTRIB_CODENAME") ? properties.getProperty("DISTRIB_CODENAME") : isWin8() ? "Windows 8" : "";
	}
	
	/**
	 * @return the amount of virtual memory that is guaranteed to
	 *         be available to the running process in bytes,
	 *         or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getCommittedVirtualMemorySize() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getCommittedVirtualMemorySize();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the amount of virtual memory that is guaranteed to
	 *         be available to the running process
	 */
	public static String getCommittedVirtualMemorySizeInString() {
	
		long value = getCommittedVirtualMemorySize();
		
		return value >= 0 ? new FileSize(value).toString() : "";
	}
	
	/**
	 * @return Debian Version or Empty String
	 */
	public static String getDebianVersion() {
	
		String line = "";
		
		//
		if(isDebianLinux()) {
			
			//
			try (BufferedReader reader = new BufferedReader(new FileReader(new File("/etc/debian_version")))) {
				
				//
				while((line = reader.readLine()) != null) {
					
					//
					if( !line.isEmpty()) {
						
						return line.trim();
					}
				}
				
			}catch(Throwable throwable) {
				
				throwable.printStackTrace();
			}
		}
		
		return "";
	}
	
	/**
	 * @return The Description
	 */
	public static String getDescription() {
	
		Properties properties = getLSBRelease();
		
		return properties != null && properties.containsKey("DISTRIB_DESCRIPTION") ? properties.getProperty("DISTRIB_DESCRIPTION") : "";
	}
	
	/**
	 * @return Distribution or Empty String
	 */
	public static String getDistribution() {
	
		//
		if(isUbuntuLinux()) {
			
			return getUbuntuRelease().getProperty("DISTRIB_ID");
			
		}else if(isDebianLinux()) {
			
			return "Debian";
			
		}else if(isFedoraLinux()) {
			
			return "Fedora";
			
		}else if(isGentooLinux()) {
			
			return "Gentoo";
			
		}else if(isKnoppixLinux()) {
			
			return "Knoppix";
			
		}else if(isMandrakeLinux()) {
			
			return "Mandrake";
			
		}else if(isMandrivaLinux()) {
			
			return "Mandriva";
			
		}else if(isRedHatLinux()) {
			
			return "RedHat";
			
		}else if(isSlackwareLinux()) {
			
			return "Slackware";
			
		}else if(isSuSELinux()) {
			
			return "SuSE";
		}
		
		return "";
	}
	
	/**
	 * @return the amount of free physical memory in bytes, or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getFreePhysicalMemorySize() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getFreePhysicalMemorySize();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the amount of free physical memory
	 */
	public static String getFreePhysicalMemorySizeInString() {
	
		long value = getFreePhysicalMemorySize();
		
		return value >= 0 ? new FileSize(value).toString() : "";
	}
	
	/**
	 * @return the amount of free swap space in bytes, or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getFreeSwapSpaceSize() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getFreeSwapSpaceSize();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the amount of free swap space
	 */
	public static String getFreeSwapSpaceSizeInString() {
	
		long value = getFreeSwapSpaceSize();
		
		return value >= 0 ? new FileSize(value).toString() : "";
	}
	
	/**
	 * @return The Kernel Version (OS Version)
	 */
	public static String getKernelVersion() {
	
		return getOsVersion();
	}
	
	/**
	 * @return The Linux Version or OS Version
	 */
	public static String getLinuxVersion() {
	
		String version = "";
		
		//
		if(isUbuntuLinux()) {
			
			version = getUbuntuRelease().getProperty("DISTRIB_RELEASE");
			
		}else if(isDebianLinux()) {
			
			version = getDebianVersion();
			
		}else if(isFedoraLinux()) {
			
			version = getOsVersion();
			
		}else if(isGentooLinux()) {
			
			version = getOsVersion();
			
		}else if(isKnoppixLinux()) {
			
			version = getOsVersion();
			
		}else if(isMandrakeLinux()) {
			
			version = getOsVersion();
			
		}else if(isMandrivaLinux()) {
			
			version = getOsVersion();
			
		}else if(isRedHatLinux()) {
			
			version = getOsVersion();
			
		}else if(isSlackwareLinux()) {
			
			version = getOsVersion();
			
		}else if(isSuSELinux()) {
			
			version = getOsVersion();
		}
		
		return CheckUtil.isNullOrAbsoluteEmpty(version) ? getOsVersion() : version;
	}
	
	/**
	 * @return {@link Properties} or null
	 */
	public static Properties getLSBRelease() {
	
		try {
			//
			if(new File("/etc/lsb-release").exists()) {
				
				final Properties properties = new Properties();
				
				properties.load(new FileReader(new File("/etc/lsb-release")));
				
				return properties;
			}
			
		}catch(Throwable throwable) {
			
			throwable.printStackTrace();
		}
		
		return null;
	}
	
	/**
	 * Returns the name of the current operating system platform as listed in the System properties.
	 */
	public static final String getName() {
	
		return isPosix() ? getDistribution() : getOsName();
	}
	
	/**
	 * Returns the "recent cpu usage" for the Java Virtual Machine process.
	 * This value is a double in the [0.0,1.0] interval. A value of 0.0 means
	 * that none of the CPUs were running threads from the JVM process during
	 * the recent period of time observed, while a value of 1.0 means that all
	 * CPUs were actively running threads from the JVM 100% of the time
	 * during the recent period being observed. Threads from the JVM include
	 * the application threads as well as the JVM internal threads. All values
	 * betweens 0.0 and 1.0 are possible depending of the activities going on
	 * in the JVM process and the whole system. If the Java Virtual Machine
	 * recent CPU usage is not available, the method returns a negative value.
	 *
	 * @return the "recent cpu usage" for the Java Virtual Machine process;
	 *         a negative value if not available.
	 * @since 1.7
	 */
	public static double getProcessCpuLoad() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getProcessCpuLoad();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the CPU time used by the process in nanoseconds,
	 *         or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getProcessCpuTime() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getProcessCpuTime();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the CPU time used by the process
	 */
	public static String getProcessCpuTimeInString() {
	
		long value = getProcessCpuTime();
		
		return value >= 0 ? DateUtil.formatTime(value, TimeUnit.NANOSECONDS, TimeUnit.HOURS) : "";
	}
	
	/**
	 * Returns a String representing this OperatingSystem type.
	 */
	public static String getString() {
	
		final StringBuilder strOS = new StringBuilder();
		
		strOS.append(I18N.system().name(getName()) + SystemProperties.getLineSeparator());
		strOS.append(I18N.system().version(getVersion()) + SystemProperties.getLineSeparator());
		
		//
		if( !getCodeName().isEmpty()) {
			
			strOS.append(I18N.system().codename(getCodeName()) + SystemProperties.getLineSeparator());
		}
		
		//
		if( !getDescription().isEmpty()) {
			
			strOS.append(I18N.system().description(getDescription()) + SystemProperties.getLineSeparator());
		}
		
		strOS.append(I18N.system().architecture(getArchitecture()));
		
		return strOS.toString();
	}
	
	/**
	 * Returns the "recent cpu usage" for the whole system. This value is a
	 * double in the [0.0,1.0] interval. A value of 0.0 means that all CPUs
	 * were idle during the recent period of time observed, while a value
	 * of 1.0 means that all CPUs were actively running 100% of the time
	 * during the recent period being observed. All values betweens 0.0 and
	 * 1.0 are possible depending of the activities going on in the system.
	 * If the system recent cpu usage is not available, the method returns a
	 * negative value.
	 *
	 * @return the "recent cpu usage" for the whole system; a negative
	 *         value if not available.
	 * @since 1.7
	 */
	public static double getSystemCpuLoad() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getSystemCpuLoad();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the total amount of physical memory in bytes, or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getTotalPhysicalMemorySize() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getTotalPhysicalMemorySize();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the total amount of physical memory
	 */
	public static String getTotalPhysicalMemorySizeInString() {
	
		long value = getTotalPhysicalMemorySize();
		
		return value >= 0 ? new FileSize(value).toString() : "";
	}
	
	/**
	 * @return the total amount of swap space in bytes, or <tt>-1</tt> if this operation is not supported.
	 */
	public static long getTotalSwapSpaceSize() {
	
		//
		if(SYSTEM_MX_BEAN instanceof com.sun.management.OperatingSystemMXBean) {
			
			return ((com.sun.management.OperatingSystemMXBean)SYSTEM_MX_BEAN).getTotalSwapSpaceSize();
			
		}else {
			
			return -1;
		}
	}
	
	/**
	 * @return the total amount of swap space
	 */
	public static String getTotalSwapSpaceSizeInString() {
	
		long value = getTotalSwapSpaceSize();
		
		return value >= 0 ? new FileSize(value).toString() : "";
	}
	
	/**
	 * Returns the OS representing the current operating system platform.
	 */
	public static final Type getType() {
	
		final String osName = getOsName().toUpperCase();
		
		//
		if(getOsName().toUpperCase().indexOf("MAC OS") >= 0) {
			
			return Type.MAC;
			
		}else if(osName.indexOf("WINDOWS") >= 0) {
			
			return Type.WINDOWS;
			
		}else {
			
			return Type.POSIX;
		}
	}
	
	/**
	 * Keys:<br>
	 * <br>
	 * DISTRIB_ID<br>
	 * DISTRIB_RELEASE<br>
	 * DISTRIB_CODENAME<br>
	 * DISTRIB_DESCRIPTION<br>
	 * 
	 * @return if is Ubuntu Derivation return the {@link Properties}, else return null
	 */
	public static Properties getUbuntuRelease() {
	
		return isUbuntuLinux() ? getLSBRelease() : null;
	}
	
	/**
	 * Returns the first readable and writable user data folder appropriate to this or null
	 */
	public static File getUserDataFolder() {
	
		final File[] folders = getUserDataFolders();
		
		//
		for(final File folder : folders) {
			
			//
			if(folder.canRead() && folder.canWrite()) {
				
				return folder;
			}
		}
		
		return null;
	}
	
	/**
	 * Returns a list of the preferred locations for storing user- specific data in descending order.
	 */
	public static File[] getUserDataFolders() {
	
		final List<File> folders = new ArrayList<File>();
		
		//
		if(isMac()) {
			
			folders.add(new File(getUserHome() + "/Library/Application Support"));
			
		}else if(isWindows()) {
			
			//
			if(isWinNTSerie()) {
				
				folders.add(new File(getUserHome() + "\\\\Application Data"));
				folders.add(new File(getUserHome() + "\\\\Local Settings\\\\Application Data"));
				
			}else if(isWinVista()) {
				
				folders.add(new File(getUserHome() + "\\\\AppData"));
				folders.add(new File(getUserHome() + "\\\\AppData\\\\Local"));
			}
		}
		
		folders.add(new File(getUserHome()));
		
		final File[] files = new File[folders.size()];
		
		return folders.toArray(files);
	}
	
	/**
	 * @return The Version
	 */
	public static String getVersion() {
	
		return getType() == Type.POSIX ? getLinuxVersion() : getOsVersion();
	}
	
	/**
	 * @param architecture
	 */
	public static boolean isArchitecture(String architecture) {
	
		String architectureName = getArchitecture();
		
		//
		if(architectureName == null || architectureName.length() <= 0) {
			
			return false;
		}
		
		// case insensitive compare...
		architectureName = architectureName.toLowerCase();
		
		architecture = architecture.toLowerCase();
		
		//
		if(architectureName.indexOf(architecture) >= 0) {
			
			return true;
		}
		
		return false;
	}
	
	/**
	 * @return is ArchLinux
	 */
	public static boolean isArchLinux() {
	
		Properties properties = getLSBRelease();
		
		//
		if(properties != null && properties.containsKey("DISTRIB_ID")) {
			
			Object value = properties.get("DISTRIB_ID");
			
			return value != null && value.toString().trim().toUpperCase().equals("ARCH") ? true : false;
		}
		
		return false;
	}
	
	/**
	 * @return is Debian
	 */
	public static boolean isDebianLinux() {
	
		return new File("/etc/debian_version").exists();
	}
	
	/**
	 * @return is Fedora
	 */
	public static boolean isFedoraLinux() {
	
		return new File("/etc/fedora-release").exists();
	}
	
	/**
	 * @return is Gentoo
	 */
	public static boolean isGentooLinux() {
	
		return new File("/etc/gentoo-release").exists();
	}
	
	/**
	 * @return is Knoppix
	 */
	public static boolean isKnoppixLinux() {
	
		return new File("/etc/knoppix_version").exists();
	}
	
	/**
	 * Returns whether or not the current operating system is the Apple Macintosh OS X platform.
	 */
	public static final boolean isMac() {
	
		return getOsName().toUpperCase().indexOf("MAC OS") >= 0;
	}
	
	/**
	 * @return is Mandrake
	 */
	public static boolean isMandrakeLinux() {
	
		return new File("/etc/mandrake-release").exists();
	}
	
	/**
	 * @return is Mandriva
	 */
	public static boolean isMandrivaLinux() {
	
		return new File("/etc/mandriva-release").exists();
	}
	
	/**
	 * Returns whether or not the current operating system is a Posix-compatible platform (Unix, Linux, Solaris, etc).
	 */
	public static final boolean isPosix() {
	
		return getType() == Type.POSIX;
	}
	
	/**
	 * @return is RedHat
	 */
	public static boolean isRedHatLinux() {
	
		return new File("/etc/redhat-release").exists();
	}
	
	/**
	 * @return is Slackware
	 */
	public static boolean isSlackwareLinux() {
	
		return new File("/etc/slackware-version").exists();
	}
	
	/**
	 * @return is Solaris 10
	 */
	public static boolean isSolaris10() {
	
		return isSun() && getVersion().equals("5.10");
	}
	
	/**
	 * @return is Sun
	 */
	public static boolean isSun() {
	
		return getName().indexOf("sun") >= 0;
	}
	
	/**
	 * @return is Sun Sparc
	 */
	public static boolean isSunSparc() {
	
		return isSun() && isArchitecture("sparc");
	}
	
	/**
	 * @return is Sun X86
	 */
	public static boolean isSunX86() {
	
		return isSun() && isArchitecture("x86");
	}
	
	/**
	 * @return is SuSE
	 */
	public static boolean isSuSELinux() {
	
		return new File("/etc/SuSE-release").exists();
	}
	
	/**
	 * @return is Ubuntu
	 */
	public static boolean isUbuntuLinux() {
	
		Properties properties = getLSBRelease();
		
		//
		if(properties != null && properties.containsKey("DISTRIB_ID")) {
			
			Object value = properties.get("DISTRIB_ID");
			
			return value != null && value.toString().trim().toUpperCase().equals("UBUNTU") ? true : false;
		}
		
		return false;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 2000 platform.
	 */
	public static boolean isWin2000() {
	
		return getName().indexOf("2000") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 2003 platform.
	 */
	public static boolean isWin2003() {
	
		return getName().indexOf("2003") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 7 platform.
	 */
	public static boolean isWin7() {
	
		return getName().indexOf("7") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 8 platform.
	 */
	public static boolean isWin8() {
	
		return isWinNT() && getVersion().equals("6.2");
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 95 platform.
	 */
	public static boolean isWin95() {
	
		return getName().indexOf("95") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 98 platform.
	 */
	public static boolean isWin98() {
	
		return getName().indexOf("98") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows 9X platform (Windows 95/98/ME).
	 */
	public static boolean isWin9X() {
	
		return isWin95() || isWin98() || isWinME();
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft Windows platform.
	 */
	public static final boolean isWindows() {
	
		return getOsName().toUpperCase().indexOf("WINDOWS") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows ME platform.
	 */
	public static boolean isWinME() {
	
		return getName().indexOf("me") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows NT platform.
	 */
	public static boolean isWinNT() {
	
		return getName().indexOf("NT") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows NT platform (Windows NT/2000/2003/XP).
	 */
	public static boolean isWinNTSerie() {
	
		return isWinNT() || isWin2000() || isWin2003() || isWinXP();
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows Vista platform.
	 */
	public static boolean isWinVista() {
	
		return getName().indexOf("Vista") >= 0;
	}
	
	/**
	 * Returns whether or not the current operating system is a Microsoft
	 * Windows XP platform.
	 */
	public static boolean isWinXP() {
	
		return getName().indexOf("XP") >= 0;
	}
	
	/**
	 * Returns a String representing this OperatingSystem type.
	 */
	@Override
	public String toString() {
	
		return getString();
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxUtils
	 * @package org.jxUtils.system
	 * @date 07/09/2013 - 09:45:37
	 */
	public static enum Type {
		MAC,
		POSIX,
		WINDOWS
	}
}
